/*           Copyright(c) 2005-2008 Dave Sexton            *
 *                   HtmlEditor Control                    *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using DaveSexton.Controls.Native;
using System.IO;

namespace DaveSexton.Controls
{
	internal static class NativeMethods
	{
		private const int ERROR_INSUFFICIENT_BUFFER = 122;

		#region Imports
    [DllImport("Wininet.dll", PreserveSig = true, SetLastError = true, CharSet = CharSet.Unicode)]
		private static extern IntPtr RetrieveUrlCacheEntryStream(string unescapedUrl, IntPtr info,
			ref uint lpcbCacheEntryInfo, [MarshalAs(UnmanagedType.Bool)] bool fRandomRead, uint dwReserved);

		[DllImport("Wininet.dll", PreserveSig = true, SetLastError = true)]
		[return: MarshalAs(UnmanagedType.Bool)]
		private static extern bool UnlockUrlCacheEntryStream(IntPtr hUrlCacheStream, uint dwReserved);

		[DllImport("Wininet.dll", PreserveSig = true, SetLastError = true)]
		[return: MarshalAs(UnmanagedType.Bool)]
		private static extern bool ReadUrlCacheEntryStream(IntPtr hUrlCacheStream, uint dwLocation,
			[In, Out] IntPtr lpBuffer, ref uint lpdwLen, uint reserved);
		#endregion

		#region Wrappers
		public static Stream GetUrlCacheEntry(string unescapedUrl)
		{
			INTERNET_CACHE_ENTRY_INFO info;

			IntPtr handle = RetrieveUrlCacheEntryStream(unescapedUrl, false, out info);

			try
			{
				return new MemoryStream(ReadUrlCacheEntryStream(handle, 0, info.dwSizeLow));
			}
			finally
			{
				if (!UnlockUrlCacheEntryStream(handle, 0))
					Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
			}
		}

		private static IntPtr RetrieveUrlCacheEntryStream(string unescapedUrl, bool randomAccess, out INTERNET_CACHE_ENTRY_INFO info)
		{
			IntPtr buffer = IntPtr.Zero;
			uint length = 0;

			try
			{
				// first, get the size of the buffer
				RetrieveUrlCacheEntryStream(unescapedUrl, buffer, ref length, randomAccess, 0);

				int error = Marshal.GetLastWin32Error();

				// ERROR_INSUFFICIENT_BUFFER indicates that length was invalid and has now been set to the appropriate 
				// value by the function.
				// If the function returns a valid handle, then length is the number of bytes copied into the buffer.
				if (error != ERROR_INSUFFICIENT_BUFFER)
					Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());

				int newLength = unchecked((int) length);

				buffer = Marshal.AllocHGlobal(newLength);
				Marshal.WriteInt32(buffer, newLength);

				IntPtr stream = RetrieveUrlCacheEntryStream(unescapedUrl, buffer, ref length, randomAccess, 0);

				if (stream == IntPtr.Zero)
					Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());

				info = (INTERNET_CACHE_ENTRY_INFO) Marshal.PtrToStructure(buffer, typeof(INTERNET_CACHE_ENTRY_INFO));

				return stream;
			}
			finally
			{
				Marshal.FreeHGlobal(buffer);
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Interoperability", "CA1404:CallGetLastErrorImmediatelyAfterPInvoke")]
		private static byte[] ReadUrlCacheEntryStream(IntPtr stream, uint offset, uint length)
		{
			byte[] bytes = new byte[length];
			IntPtr buffer = Marshal.AllocHGlobal(unchecked((int) length));

			try
			{
				while (!ReadUrlCacheEntryStream(stream, offset, buffer, ref length, 0))
				{
					int error = Marshal.GetLastWin32Error();

					// ERROR_INSUFFICIENT_BUFFER indicates that length was invalid and has now been set to the appropriate 
					// value by the function.
					// If the function returns TRUE, then length is the number of bytes copied into the buffer.
					if (error != ERROR_INSUFFICIENT_BUFFER)
						Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());

					Marshal.ReAllocHGlobal(buffer, (IntPtr) unchecked((int) length));
					bytes = new byte[length];
				}

				for (uint i = 0; i < length; i++)
					bytes[i] = Marshal.ReadByte(buffer, unchecked((int) i));
			}
			finally
			{
				Marshal.FreeHGlobal(buffer);
			}

			return bytes;
		}
		#endregion
	}
}
